<?php

namespace app\common\api;
use Elasticsearch\ClientBuilder;

class Esearch {


    /**
     * 端口
     * @var array
     * @author Sam
     * @date 2023-01-13
     */
    private $hosts = ['127.0.0.1:9200'];
    /**
     * 类单例
     *
     * @var object
     */
    private static $instance;
    /**
     * Es的连接句柄
     *
     * @var object
     */
    private $elastic;
    /**
     * 私有化构造函数，防止类外实例化
     *
     * @param
     */
    private function __construct ()
    {
        $this->elastic = ClientBuilder::create()->setHosts($this->hosts)->build();
    }
    /**
     * 私有化克隆函数，防止类外克隆对象
     */
    private function __clone ()
    {}
    /**
     * 类的唯一公开静态方法，获取类单例的唯一入口
     *
     * @return object
     */
    public static function getEslasticInstance ()
    {
        if (! (self::$instance instanceof self)) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    /**
     * 获取Es的连接实例
     *
     * @return
     */
    public function getEsConn ()
    {
        return $this->elastic;
    }
    /**
     * 需要在单例切换的时候做清理工作
     */
    public function __destruct ()
    {
        // self::$instance->elastic->close();
        // self::$instance = NULL;
    }

    /**
     * 分词器查询
     * @param String $index  索引
     * @param String $text  关键字
     * @param string $analyze 分词模式  ik_smart   ik_max_word  standard
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function analyze(String $index, String $text, String $analyzer = 'ik_smart'){
        $params = [ 
            'index' => $index,
            'body'  => [
                'text' =>  $text,
                'analyzer' => $analyzer
            ],
            
        ]; 
      
        return $this->elastic->indices()->analyze($params);
    }

    /**
     * 搜索全部
     * @param String $index 索引
     * @param Int $limit 每页数量
     * @param Int $page 页码
     * @return void
     * @author Sam
     * @date 2023-01-17
     */
    public function match_all(String $index, $page=1,$limit=10){
        $offset = ((Int)$page-1)*(Int)$limit;//偏移量
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'match_all'=>new \stdClass()
                ],
               'size' => $limit,
               'from' => $offset,
               'sort' => [
                 'id' => [
                    'order' => 'desc'
                    ]
                ],
            ]
        ]; 
        return $this->elastic->search($params);
    }

    /**
     * 组合查询
     * @return void
     * @author Sam
     * @date 2023-01-17
     */
    public function bool_query($index,$must,$must_not,$should,$page,$limit){
        $offset  = ((Int)$page-1)*(Int)$limit;//偏移量
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query'=>[
                    'bool' =>[
                        'must'      => $must ?? new \stdClass(),
                        'must_not'  => $must_not ?? new \stdClass(),
                        'should'    => $should ?? new \stdClass()
                    ],
                ],
                'highlight' => [
                    'require_field_match' => 'false',
                    'fields' => [
                        '*' =>[
                            "pre_tags"=>"<b style='color:red'>",
                            "post_tags"=>"</b>"
                        ]
                    ]
                ],
                'sort' => [
                    '_score' => [
                       'order' => 'desc'
                        ],
                    'id' => [
                        'order' => 'desc'
                        ]
                   ],
               'size' => $limit,
               'from' => $offset
            ]
        ]; 
  
        return $this->elastic->search($params);
    }
    /**
     * 用于搜索单个字段，首先会针对查询语句进行解析（经过 analyzer），主要是对查询语句进行分词，分词后查询语句的任何一个词项被匹配，文档就会被搜到，默认情况下相当于对分词后词项进行 or 匹配操作
     * @param [string] $index 索引
     * @param [string] $field 字段
     * @param [string] $content 内容
     * @param [string] $size 每页显示条数
     * @param [string] $from 页码
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function match_query($index,$field,$content,$page=1,$limit=10){
        $offset = ((Int)$page-1)*(Int)$limit;//偏移量
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'match'=>[
                        $field => $content
                    ]
                ],
                'highlight' => [
                    'require_field_match' => 'false',
                    'fields' => [
                        '*' =>[
                            "pre_tags"=>"<b style='color:red'>",
                            "post_tags"=>"</b>"
                        ]
                    ]
                ],

               'size' => $limit,
               'from' => $offset
            ]
        ]; 
     
        return $this->elastic->search($params);
    }


    /**
     * match_phrasematch_phrase query 首先会把 query 内容分词，分词器可以自定义，同时文档还要满足以下两个条件才会被搜索到：分词后所有词项都要出现在该字段中（相当于 and 操作）。字段中的词项顺序要一致。
     * @param [string] $index 索引
     * @param [string] $field 字段
     * @param [string] $content 内容
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function match_phrase($index,$field,$content){
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'match_phrase'=>[
                        $field => $content
                    ]
                ],
                'highlight' => [
                    'fields' => [
                        $field =>[
                            "pre_tags"=>"<b style='color:red'>",
                            "post_tags"=>"</b>"
                        ]
                    ]
                ],
                'sort'=>[
                    'id'=>'desc'
                ]
            ]
        ]; 
     
        return $this->elastic->search($params);
    }

    /**
     * multi_match 是 match 的升级，用于搜索多个字段
     * @param String $index 索引
     * @param Array $fields 多个字段
     * @param String $content 内容
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function multi_match_query(String $index,Array $fields, String $content){
        $highlight  = [];
        foreach ($fields as $key => $value) {
            $highlight[$key][$value] = [
                "pre_tags"=>"<b style='color:red'>",
                "post_tags"=>"</b>"
            ];
        }
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'multi_match'=>[
                        'query' =>  $content,
                        'fields' => $fields
                    ]
                ],
             
                'highlight' => [
                    'fields' => $highlight
                ],
                'sort'=>[
                    'id'=>'desc'
                ]
            ]
        ]; 
     
        return $this->elastic->search($params);
    }

    /**
     * 词项查询function 文查询在执行查询之前会分析查询字符串，词项查询时对倒排索引中存储的词项进行精确匹配操作。词项级别的查询通常用于结构化数据，如数字、日期和枚举类型。
     * 避免 term 查询对 text 字段使用查询。默认情况下，Elasticsearch 针对 text 字段的值进行解析分词，这会使查找 text 字段值的精确匹配变得困难。要搜索 text 字段值，需改用 match 查询。
     * @param String $index 索引
     * @param String $field 字段
     * @param String $content 内容
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function term_query(String $index,String $field,String $content,$page=1,$limit=10){
        $offset = ((Int)$page-1)*(Int)$limit;//偏移量
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'term'=>[
                        $field => $content,
                    ]
                ],
             
                'highlight' => [
                    'fields' => [
                        $field =>[
                            "pre_tags"=>"<b style='color:red'>",
                            "post_tags"=>"</b>"
                        ]
                    ]
                ],
                'sort'=>[
                    'id'=>'desc'
                ],
                'size' => $limit,
                'from' => $offset
            ]
        ]; 
     
        return $this->elastic->search($params);
    }

    /**
     * 区间查询即范围查询，用于匹配在某一范围内的数值型、日期类型或者字符串型字段的文档，比如搜索哪些书籍的价格在 50 到 100之间、哪些书籍的出版时间在 2015 年到 2019 年之间。使用 range 查询只能查询一个字段，不能作用在多个字段上。
     * @param String $index 索引
     * @param String $field 字段
     * @param array $range 范围
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function range_query(String $index,String $field,Array $range = []){
        $params = [ 
            'index' => $index,
            'type' => 'my_type',
            'body' => [
                'query' =>[
                    'range'=>[
                        $field => [
                            "gt" => $range[0],
                            "lte"=>$range[1]
                        ],
                    ]
                ],

                'sort'=>[
                    'id'=>'desc'
                ]
            ]
        ]; 
     
        return $this->elastic->search($params);
    }

  

    /**
     * 索引查询
     * @param String $index
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function get_index( String $index = ''){
        $params = [
            'index' => $index
        ];
        return  $this->elastic->indices()->get($params);
    }

        /**
     * 创建索引
     * @param string $index 索引名 对应mysql表名  
     * @param array $param 索引名 
     * @return void
     * @author Sam
     * @date 2023-01-13
     * ##    'settings' => [
                    'number_of_shards' => 3, # 分片数量
                    'number_of_replicas' => 2 # 副本数量
                ],
                'mappings' => [  映射
                    'my_type' => [
                        '_source' => [
                            'enabled' => true
                        ],
                        'properties' =>[  字段
                            'id' => [
                                'type' => 'long', 类型
                                'store' => true, 是否储存
                                'index'  => true 是否索引
                            ],
                            'thumb' => [
                                'type' => 'text',
                                'store' => true,
                                'index'  => false
                            ],
                            'detail' => [
                                'type' => 'text',
                                'store' => true,
                                'index'  => true,
                                'analyzer' =>'ik_max_word' 分词器 
                            ],
                        ]
                    ]
                ]
     */
    public function create_index($index,$param=[]){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index
        ];
        if(count($param)){
            $params['body'] = $param;
        }
       
        if($this->elastic->indices()->exists(['index'=>$index])){
            exception('索引已存在');
        }
        return  $this->elastic->indices()->create($params);
    }

    /**
     * 删除索引
     * @param [string] $index 索引名 对应mysql表名
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function delete_index($index){
        if(!trim($index)) exception('索引不得为空');
        $params = ['index' => $index];
        return $this->elastic->indices()->delete($params);
    }

    /**
     * 修改索引参数
     * @param String $index 索引 参数
     * @param array $param
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function update_index( String $index,Array $param =[]){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index
        ];
        if(count($param)){
            $params['body'] = $param;
        }
        return  $this->elastic->indices()->putSettings($params);
    }

    /**
     * 修改索引映射
     * @param [type] $index
     * @param array $param
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function update_index_mapping($index,$param = []){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index,
            'type'  => 'my_type',
        ];
        if(count($param)){
            $params['body'] = $param;
        }
        return  $this->elastic->indices()->putMapping($params);
    }

    /**
     * 创建文档
     * @param String $index
     * @param Int $id 
     * @param array $param
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function insert_doc(String $index,Int $id,Array $param =[]){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index,
            'id'    => $id,
            'type'  => 'my_type'
        ];
        if(count($param)){
            $params['body'] = $param;
        }
        return  $this->elastic->index($params);
    }

    /**
     * 搜索文档
     * @param String $index
     * @param Int $id
     * @return void
     * @author Sam
     * @date 2023-01-14
     */
    public function get_doc(String $index,Int $id){
        $params = [
            'index' => $index,
            'id'    => $id,
            'type'  => 'my_type'
        ];
        return  $this->elastic->get($params);
    }
    /**
     * 更新文档
     * @param String $index
     * @param Int $id
     * @param array $param
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function update_doc(String $index,Int $id,Array $param =[]){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index,
            'id'    => $id,
            'type'  => 'my_type'
        ];
        if(count($param)){
            $params['body']['doc'] = $param;
        }
        return  $this->elastic->update($params);
    }

    /**
     * 删除文档
     * @param String $index
     * @param Int $id
     * @return void
     * @author Sam
     * @date 2023-01-13
     */
    public function delete_doc(String $index,Int $id){
        if(!trim($index)) exception('索引不得为空');
        $params = [
            'index' => $index,
            'id'    => $id,
            'type'  => 'my_type'
        ];
        return  $this->elastic->delete($params);
    }
}
